CloudFrontのジオターゲティングをngx_mruby&Redisで判別する
ども、大瀧です。
このブログ記事はmod_mruby ngx_mruby Advent Calendar 2014の14日目です。
昨日は@matsumotoryさんの地理情報を使ってmod_mrubyとngx_mrubyでプログラマブルにアクセス制御でした。今日も続けての地理情報ネタです。
以前、Amazon CloudFrontとEC2(Nginx)で作る国・地域対応Webサイト構築という記事でCloudFrontが付与する地理情報をNginxで判別する設定を紹介しました。
この構成だと、国とリダイレクト先を追加/変更するたびにnginxの設定をリロードしなければならず、またMulti-AZとして複数のインスタンスを立てるときに設定を同期させなければなりません。
そこで今回は、Nginxの動的構成を実現するngx_mrubyを用いてAmazon ElastiCache for Redisのデータを読み出す形で国とリダイレクト先を対応させる処理を実装してみます。
Nginxとngx_mrubyの設定
Nginxの構成のうち、mrubyを呼び出す部分を抜粋しました。
: location / { mruby_set $redirect /usr/local/nginx/hook/redirect.rb; rewrite ^ $redirect permanent; } :
今回はリダイレクト先を動的に変更すれば良いので、rewriteディレクティブで変数をリダイレクト先とし、mrubyスクリプトの戻り値を変数にセットするmruby_setディレクティブを利用しました。呼び出すmrubyスクリプトredirect.rbは以下です。
#location / { # mruby_set $redirect /usr/local/nginx/hook/redirect.rb; # rewrite ^ $redirect permanent; #} default_redirect_url = 'http://example.com/jp/' request = Nginx::Request.new country = request.headers_in['Cloudfront-Viewer-Country'] if !(country.nil? || country.empty?) redis = Redis.new "<REDIS_HOSTNAME>", 6379 redirect_url = redis.get country if !redirect_url.nil? redirect_url else default_redirect_url end else default_redirect_url end
7,8行目でHTTPヘッダからCloudFrontが付与するCloudfront-Viewer-Countryを参照し、10行目でセットされているかを判別、11,12行目でヘッダの値からリダイレクト先をRedisから読み出しています。ヘッダが空だった場合やRedisに登録がなかった場合のデフォルト値として、http://example.com/jp/をリダイレクト先として返すようにしています。
動作確認
ngx_mrubyを最も簡単に動作させる方法は、Dockerコンテナでの実行です。今回はAmazon LinuxにDockerをインストールして試してみました。
$ sudo yum install -y docker : $ sudo service docker start $ sudo chkconfig docker on $ sudo docker version Client version: 1.3.3 Client API version: 1.15 Go version (client): go1.3.3 Git commit (client): c78088f/1.3.3 OS/Arch (client): linux/amd64 Server version: 1.3.3 Server API version: 1.15 Go version (server): go1.3.3 Git commit (server): c78088f/1.3.3 $
OKですね。
Redis(ElastiCache)の準備
続いて、mrubyから接続するRedisノードをAmazon ElastiCache for Redis 1ノードを作成しました。redis-cliで接続し、データを登録しておきます。redis-cliもDockerコンテナで実行してみました。
$ sudo docker run -it --rm --link redis:redis dockerfile/redis bash -c 'redis-cli -h counties.XXXXXX.0001.apne1.cache.amazonaws.com' counties.XXXXXX.0001.apne1.cache.amazonaws.com:6379> mset JP http://example.com/jp/ CN http://example.com/cn/ TW http://example.com/tw/ OK counties.XXXXXX.0001.apne1.cache.amazonaws.com:6379> keys * 1) "JP" 2) "CN" 3) "TW" 4) "ElastiCacheMasterReplicationTimestamp" counties.XXXXXX.0001.apne1.cache.amazonaws.com:6379>
ngx_mrubyの実行
ngx_mrubyの作者@matsumotoryさん作のmatsumotory/ngx-mrubyというDockerイメージがDocker Hubで公開されているので、こちらをベースに以下のDockerfileでアレンジしてみました。
FROM matsumotory/ngx-mruby EXPOSE 80 EXPOSE 443 ADD hook /usr/local/nginx/hook ADD nginx.conf /usr/local/nginx/conf/nginx.conf CMD ["/usr/local/nginx/sbin/nginx"]
docker buildコマンドでDockerイメージを作成します。
$ docker build -t takipone/ngx-mruby . Sending build context to Docker daemon 5.12 kB Sending build context to Docker daemon Step 0 : FROM matsumotory/ngx-mruby ---> cd4b597cfdb3 Step 1 : EXPOSE 80 ---> Using cache ---> cfef66f7d472 Step 2 : EXPOSE 443 ---> Using cache ---> 56dc98e500a5 Step 3 : ADD hook /usr/local/nginx/hook ---> 674c0b5e921a Removing intermediate container 9c887712b5b6 Step 4 : ADD nginx.conf /usr/local/nginx/conf/nginx.conf ---> 47b34f8aa06b Removing intermediate container 8460eb48abc8 Step 5 : CMD /usr/local/nginx/sbin/nginx ---> Running in 4e594377e9d5 ---> 5e48ce97d4ae Removing intermediate container 4e594377e9d5 Successfully built 5e48ce97d4ae $
では、作成したDockerイメージからコンテナを実行します。
$ sudo docker run -d -p 80:80 takipone/ngx-mruby eb00d07bc7d76d6787baf839e3ca8d806776c68081b7ba5d28eac9824efdcba4 $
CloudFrontが付与するCloudFront-Viewer-Countryヘッダをcurlコマンドで再現し、動作を確認します。
$ curl -I --header "CloudFront-Viewer-Country:JP" http://localhost/ HTTP/1.1 301 Moved Permanently Server: nginx/1.7.7 Date: Sun, 14 Dec 2014 13:55:29 GMT Content-Type: text/html Content-Length: 184 Connection: keep-alive Location: http://example.com/jp/ $
ちゃんと動いてますね!
まとめ
ngx_mrubyでNginxの動的処理としてRedisに接続し、リダイレクト先を判定する処理を実装しました。CloudFrontではジオターゲティング以外にも様々な情報をHTTPヘッダとしてリクエストに付与してくるので、mrubyの工夫次第でいろいろできそうですね!
なお、リクエストごとに毎回Redisに接続しにいくというのが気になる方は、mruby-userdataを検討しても良いでしょう。ただ、今回のケースではCloudFrontがレスポンスをキャッシュするためNginx自体へのアクセスは頻繁には起きないと見なしても良いと思います。
明日(といってもあと1時間くらいですが(^^;)は、qtakamitsuさんの「mod_mruby を使った Web アプリ」です。お楽しみに!